using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using UMC.SshNet;
using UMC.SshNet.Abstractions;
using UMC.SshNet.Channels;
using UMC.SshNet.Common;
using UMC.Net;

namespace UMC.ITME
{
    /// <summary>
    /// Contains operation for working with SSH Shell.
    /// </summary>
    public class HttpShell : IDisposable
    {
        SshClient _sshClient;
        IChannelSession _channel;
        UMC.Host.HttpWebSocket _webSocket;
        Entities.Device _device;
        private String _Username;
        private StringValue _ip, _userAgent, _referer;
        public HttpShell(SshClient sshClient, UMC.Host.HttpWebSocket webSocket, Entities.Device device, NameValueCollection query, String username, StringValue IP, StringValue userAgent, StringValue referer)
        {
            this._sshClient = sshClient;
            this._Username = username;
            this._device = device;
            this._webSocket = webSocket;

            uint columns = Utility.Parse(query.Get("c"), 80u);
            uint rows = Utility.Parse(query.Get("r"), 25u);
            uint width = Utility.Parse(query.Get("w"), 640u);
            uint height = Utility.Parse(query.Get("h"), 480u);
            this._userAgent = userAgent;
            this._ip = IP;
            this._referer = referer;
            this._sshClient.Connect();
            var _session = sshClient.Session;
            _webSocket.Close = this.Dispose;
            _channel = _session.CreateChannelSession();
            _channel.DataReceived += Channel_DataReceived;
            _channel.Closed += Channel_Closed;
            _session.Disconnected += Session_Disconnected;
            _session.ErrorOccured += Session_ErrorOccured;
            _webSocket.ReceiveText = _webSocket.ReceiveData = Send;

            try
            {
                _channel.Open();

                if (!_channel.SendPseudoTerminalRequest("xterm", columns, rows, width, height, null))
                {
                    throw new SshException("The pseudo-terminal request was not accepted by the server. Consult the server log for more information.");
                }

                if (!_channel.SendShellRequest())
                {
                    throw new SshException("The request to start a shell was not accepted by the server. Consult the server log for more information.");
                }
                webSocket.Send("{\"type\":\"info\",\"value\":{\"Title\":\"" + device.Caption + "\",\"Key\":\"" + device.Id + "\"}}");
                //   
            }
            catch
            {
                UnsubscribeFromSessionEvents(_session);
                _channel.Dispose();
                throw;
            }
        }

        // int bufferSize = 100;
        int inputIndex = 0;
        int count = 0;
        char[] input = new char[200];
        bool isPwd = false; bool isReadlink = false;
        bool isInput = false;

        /// <summary>
        /// Releases the unmanaged resources used by the <see cref="Stream"/> and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected void Dispose(bool disposing)
        {
            // base.Dispose(disposing);

            if (_isDisposed)
            {
                return;
            }

            if (disposing)
            {
                _isDisposed = true;
                UnsubscribeFromSessionEvents(this._sshClient.Session);

                if (_channel != null)
                {
                    _channel.DataReceived -= Channel_DataReceived;
                    _channel.Closed -= Channel_Closed;
                    _channel.Dispose();
                    _channel = null;
                    // _webSocket.Disconnect();
                }


                this._sshClient.Disconnect();
                this._sshClient.Dispose();
            }
            else
            {
                UnsubscribeFromSessionEvents(this._sshClient.Session);
                // this._sshClient.Disconnect();
            }
        }
        private bool _isDisposed = false;
        /// <summary>
        /// Unsubscribes the current <see cref="ShellStream"/> from session events.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <remarks>
        /// Does nothing when <paramref name="session"/> is <c>null</c>.
        /// </remarks>
        private void UnsubscribeFromSessionEvents(ISession session)
        {
            // _webSocket.Disconnect();
            if (session is null)
            {
                return;
            }

            session.Disconnected -= Session_Disconnected;
            session.ErrorOccured -= Session_ErrorOccured;
        }

        private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
        {
            OnRaiseError(e);
        }

        private void Session_Disconnected(object sender, EventArgs e)
        {
            _channel?.Dispose();
            _webSocket.Disconnect();
        }

        private void Channel_Closed(object sender, ChannelEventArgs e)
        {
            _webSocket.Disconnect();
            // TODO: Do we need to call dispose here ??
            Dispose();
        }

        private void Channel_DataReceived(object sender, ChannelDataEventArgs e)
        {
            OnDataReceived(e.Data);
        }

        private void OnRaiseError(ExceptionEventArgs e)
        {
            // ErrorOccurred?.Invoke(this, e);
        }
        void Send(byte[] buffer, int offset, int size)
        {
            if (buffer[offset] == 13)
            {
                isInput = false;
                if (count > 0)
                {
                    var ls = new String(input.AsSpan(0, input.Length < count ? input.Length : count));

                    isPwd = ls == "pwd";
                    isReadlink = ls.StartsWith("realpath ");
                    inputIndex = 0;
                    count = 0;
                    Activities.SiteSSHActivities.SSHLog(this._device, _Username, _ip, _userAgent, _referer, ls);
                }
            }
            _channel.SendData(buffer, offset, size);
        }

        private void OnDataReceived(byte[] data)
        {
            var lines = System.Text.Encoding.UTF8.GetString(data).Split("\r\n", StringSplitOptions.RemoveEmptyEntries);
            if (lines.Length > 0)
            {
                var str = lines[lines.Length - 1];

                if (isInput)
                {
                    if (lines.Length == 1)
                    {
                        var IsTo = false;
                        var ToValue = -1;
                        for (var i = 0; i < str.Length; i++)
                        {
                            var by = str[i];
                            switch (by)
                            {
                                case '\0':
                                case '\a':
                                    break;
                                case '\b':
                                    inputIndex--;
                                    break;
                                case '\u001b':
                                    if (i + 1 < str.Length)
                                    {
                                        if (str[i + 1] == '[')
                                        {
                                            ToValue = i + 2;
                                            i++;
                                            IsTo = true;
                                        }
                                    }
                                    break;
                                default:
                                    if (IsTo)
                                    {
                                        if ((by >= 'a' && by <= 'z') || (by >= 'A' && by <= 'Z'))
                                        {
                                            var tv = ToValue == i ? "1" : str.Substring(ToValue, i - ToValue);
                                            switch (by)
                                            {
                                                case 'K':
                                                    count = inputIndex;
                                                    break;
                                                case 'C':
                                                    inputIndex += Utility.IntParse(tv, 1);
                                                    break;
                                                case 'D':
                                                    inputIndex -= Utility.IntParse(tv, 1);
                                                    break;
                                            }
                                            IsTo = false;
                                        }
                                    }
                                    else if (input.Length > inputIndex)
                                    {
                                        input[inputIndex] = by;
                                        inputIndex++;
                                        count = inputIndex;
                                    }
                                    break;
                            }
                        }
                    }
                }
                else
                {
                    isInput = str.Contains($"[{_device.Username}@");
                    if (isPwd)
                    {
                        if (lines[0].StartsWith("/"))
                        {
                            this._webSocket.Send("{\"type\":\"view\",\"value\":{\"Path\":\"" + lines[0] + "\",\"Key\":\"" + _device.Id + "\"}}");

                        }
                    }
                    else if (isReadlink)
                    {
                        if (lines[0].StartsWith("/"))
                        {
                            this._webSocket.Send("{\"type\":\"link\",\"value\":{\"file\":\"" + lines[0] + "\",\"Key\":\"" + _device.Id + "\"}}");

                        }

                    }
                }
            }

            _webSocket.Send(data, 0, data.Length);
        }

        public void Dispose()
        {
            Dispose(true);
        }
    }
}
